using System;
using System.IO;
using System.Net;
using System.Text;

namespace JouniHeikniemi.Tools.Http {

  /// <summary>
  /// Provides functionality for programmatically uploading files 
  /// with the HTTP protocol.
  /// </summary>
  public class HttpFileUpload {
    // Prevent construction
    private HttpFileUpload() { }

    /// <summary>
    /// Holds the information about the file(s) to be uploaded.
    /// </summary>
    public struct UploadSpec {
      
      public byte[] Contents;
      public string FileName;
      public string FieldName;

      /// <summary>
      /// Creates a new upload spec based on a byte array.
      /// </summary>
      /// <param name="contents">The contents to be uploaded.</param>
      /// <param name="fileName">The file to be uploaded.</param>
      /// <param name="fieldName">The field name as which this file shall be sent to.</param>
      public UploadSpec(byte[] contents, string fileName, string fieldName) {
        this.Contents = contents;
        this.FileName = fileName;
        this.FieldName = fieldName;
      }


      /// <summary>
      /// Creates a new upload spec based on a file name.
      /// </summary>
      /// <param name="pathname"></param>
      /// <param name="fieldName"></param>
      public UploadSpec(string pathname, string fieldName) {
        using (FileStream inFile = new FileStream(pathname, FileMode.Open)) {
          byte[] inBytes = new byte[inFile.Length];
          inFile.Read(inBytes, 0, inBytes.Length);
          this.Contents = inBytes;
        }
        this.FileName = Path.GetFileName(pathname);
        this.FieldName = fieldName;
      }
    }

    /// <summary>
    /// Uploads the given file to the given url.
    /// </summary>
    /// <param name="pathname">The pathname of the file to be uploaded.</param>
    /// <param name="url">The url to which the file shall be sent.</param>
    /// <param name="fieldName">The name of the form field for the upload.</param>
    /// <param name="cookies">Cookies to be sent with the request.</param>
    /// <param name="credentials">Login credentials to be passed.</param>
    public static HttpWebResponse UploadFile(string pathname, string url, string fieldName, 
                                             CookieContainer cookies, CredentialCache credentials) {

      return Upload(url, cookies, credentials, new UploadSpec(pathname, fieldName));
    }


    /// <summary>
    /// Uploads the given byte array to the given url.
    /// </summary>
    /// <param name="data">The data to be uploaded.</param>
    /// <param name="fileName">The name to be sent as the filename.</param>
    /// <param name="url">The url to which the file shall be sent.</param>
    /// <param name="fieldName">The name of the form field for the upload.</param>
    /// <param name="cookies">Cookies to be sent with the request.</param>
    /// <param name="credentials">Login credentials to be passed.</param>
    public static HttpWebResponse UploadByteArray(byte[] data, string fileName, string url, string fieldName, 
                                                  CookieContainer cookies, CredentialCache credentials) {

      return Upload(url, cookies, credentials, new UploadSpec(data, fileName, fieldName));
    }


    /// <summary>
    /// Uploads the given data.
    /// </summary>
    /// <param name="url">The url to which the data shall be sent.</param>
    /// <param name="cookies">Cookies to be sent with the request.</param>
    /// <param name="credentials">Login credentials to be passed.</param>
    /// <param name="objects">The data to be sent.</param>
    public static HttpWebResponse Upload(string url, CookieContainer cookies, CredentialCache credentials,
                                         params UploadSpec[] objects) {

      // Initialize the request object
      HttpWebRequest req = (WebRequest.Create(url) as HttpWebRequest);
	  //req.Timeout = 30000;
      if (cookies != null) req.CookieContainer = cookies;
      if (credentials != null) req.Credentials = credentials;
      
      string boundary = Guid.NewGuid().ToString().Replace("-", "");
      req.ContentType = "multipart/form-data; boundary=" + boundary;
      req.Method = "POST";

      MemoryStream postData = new MemoryStream();
      string newLine = "\r\n";
      StreamWriter sw = new StreamWriter(postData);
      
      foreach (UploadSpec us in objects) {
        sw.Write("--" + boundary + newLine);
        sw.Write(
          "Content-Disposition: form-data; name=\"{0}\"; filename=\"{1}\"{2}",
          us.FieldName,
          us.FileName,
          newLine
          );
        sw.Write("Content-Type: application/octet-stream" + newLine + newLine);
        sw.Flush();

        postData.Write(us.Contents, 0, us.Contents.Length);
        sw.Write(newLine);
      }

      sw.Write("--{0}--{1}", boundary, newLine);
      sw.Flush();
	  

      req.ContentLength = postData.Length;
      using (Stream s = req.GetRequestStream())
        postData.WriteTo(s);
	  sw.Close();
      //postData.Close();

      return (req.GetResponse() as HttpWebResponse);
    }
  }
}